/**
 * File: ConcurrentGraph.java
 * Created by: mhaimel
 * Created on: Mar 8, 2010
 * CVS:  $Id: ConcurrentGraph.java 1.0 Mar 8, 2010 12:11:46 PM mhaimel Exp $
 */
package uk.ac.ebi.curtain.model.graph.impl;

import java.util.concurrent.atomic.AtomicReference;

import uk.ac.ebi.curtain.model.graph.Arc;
import uk.ac.ebi.curtain.model.graph.Graph;
import uk.ac.ebi.curtain.model.graph.Node;
import uk.ac.ebi.curtain.model.graph.GraphAccess.ProcessEach;
import uk.ac.ebi.curtain.model.graph.GraphAccess.TouchEach;
import uk.ac.ebi.curtain.utils.concurrent.CurtainAtomicReferenceArray;

/**
 * @author mhaimel
 *
 */
public class ConcurrentGraph<N, A> extends Graph<N, A> {


	private final CurtainAtomicReferenceArray<Node<N, A>> nodes;
	private final AtomicReference<CurtainAtomicReferenceArray<Arc<N,A>>> arcsRef = new AtomicReference<CurtainAtomicReferenceArray<Arc<N,A>>>();
	
	/**
	 * @param nodeSize
	 */
	public ConcurrentGraph(Long nodeSize) {
		super(nodeSize);
		this.nodes = new CurtainAtomicReferenceArray<Node<N,A>>(nodeSize.intValue());		
	}
	
	/**
	 * {@inheritDoc}
	 * @return 
	 */
	@Override
	protected Long doReindexArcs() {
		Long size = _calculateArcSizeFromArray();
		CurtainAtomicReferenceArray<Arc<N,A>> newArr = newArcArr(size);
		Long iNew = 0l;
		CurtainAtomicReferenceArray<Arc<N, A>> currArr = this.arcsRef.get();
		long currSize = currArr.length();
		for(Long iCurr = 0l; iCurr < currSize; ++iCurr){
			Arc<N, A> tmpArc = currArr.get(iCurr.intValue());
			if(null != tmpArc){
				tmpArc = _transferArc(iNew, tmpArc);
				newArr.set(iNew.intValue(), tmpArc);
				iNew++;
			}
		}
		this.arcsRef.set(newArr);
		return size;
	}
	
	@Override
	protected Long doBuildArcsIndex() {
		Long maxId = _maxArcIdFromNodeArray();
		Long size = maxId+1;
		CurtainAtomicReferenceArray<Arc<N, A>> array = _fillArcArrayFromNodes(size.intValue());
		this.arcsRef.set(array);		
		return size;
	}

	private CurtainAtomicReferenceArray<Arc<N, A>> newArcArr(Long arcSize) {
		return new CurtainAtomicReferenceArray<Arc<N,A>>(arcSize.intValue());
	}

	@Override
	protected Node<N, A> doGetNode(Long id) {
		return nodes.get(id.intValue());
	}

	@Override
	protected void doSetNode(Node<N, A> node) {
		nodes.set(node.getId().intValue(), node);
	}

	@Override
	protected void doSetEmptyNode(Node<N, A> node) throws IllegalStateException {
		if(!nodes.compareAndSet(node.getId().intValue(), null,node)){
			throw new IllegalStateException("Empty node expected, but Node already stored: " + node);
		}
	}

	@Override
	protected Long doGetStoredArcsCount() {
		CurtainAtomicReferenceArray<Arc<N, A>> arr = arcsRef.get();
		if(null == arr){
			return 0l;
		} else {
			return Long.valueOf(arr.length());
		}
	}

	@Override
	protected Arc<N, A> doGetArc(Long id) {
		CurtainAtomicReferenceArray<Arc<N, A>> arr = this.arcsRef.get();
		if(null == arr){
			return null;
		} else {
			return arr.get(id.intValue());
		}
	}

	@Override
	protected void doResetArcs(Long arcSize) {
		this.arcsRef.set(newArcArr(arcSize));
	}

	@Override
	protected Graph<N, A> newGraph(Long nodeSize) {
		return new ConcurrentGraph<N, A>(nodeSize);
	}

	@Override
	protected void doRemoveArc(Arc<N, A> arc) {
		this.arcsRef.get().set(arc.getId().intValue(), null);
	}

	@Override
	public <T extends ProcessEach<Arc<N,A>>> T processArcs(T operation) {
		CurtainAtomicReferenceArray<Arc<N, A>> arr = this.arcsRef.get();
		int size = arr.length();
		for(int i = 0; i < size; ++i){
			Arc<N, A> arc = arr.get(i);
			Arc<N, A> narc = operation.process(i, arc);
			if(null == narc && null != arc){
				_removeArc(arc);
				_decrementArcCount();
			}
			arr.set(i, narc);
		}
		return operation;
	}
	
	private Arc<N, A> _transferArc(Long id,Arc<N, A> arc){
		if(!arc.getId().equals(id)){
			_removeArc(arc);
			arc = _createNewArc(id, arc);
			_connectArc2Nodes(arc);
		}
		return arc;
	}

	@Override
	public <T extends TouchEach<Arc<N,A>>> T processArcs(T operation) {
		touchEach(this.arcsRef.get(),operation);
		return operation;
	}
	
	@Override
	public <T extends TouchEach<Node<N,A>>> T processNodes(T operation) {
		touchEach(this.nodes,operation);
		return operation;
	}
	
	private <E> void touchEach(CurtainAtomicReferenceArray<E> arr,TouchEach<E> op){
		int size = arr.length();
		for(int i = 0; i < size; ++i){
			E arc = arr.get(i);
			op.touch(arc);
		}
	}


	private CurtainAtomicReferenceArray<Arc<N, A>> _fillArcArrayFromNodes(int nSize) {
		CurtainAtomicReferenceArray<Arc<N, A>> nArray = new CurtainAtomicReferenceArray<Arc<N,A>>(nSize);
		int cSize = this.nodes.length();
		for(int i = 0; i < cSize; ++i){
			Node<N, A> node = this.nodes.get(i);
			for(Arc<N,A> arc : node.getLeft()){
				nArray.set(arc.getId().intValue(), arc);
			}
			for(Arc<N,A> arc : node.getRight()){
				nArray.set(arc.getId().intValue(), arc);
			}
		}
		return nArray;
	}

	@Override
	protected Long doGetArcSize() {
		return _calculateArcSizeFromArray();
	}
	
	private Long _calculateArcSizeFromArray() {
		CurtainAtomicReferenceArray<Arc<N, A>> array = this.arcsRef.get();
		int len = array.length();
		long cnt = 0;
		for(int i = 0; i < len; ++i){
			if(array.get(i) != null){
				++cnt;
			}
		}
		return cnt;
	}
	
	private Long _maxArcIdFromNodeArray() {
		int len = this.nodes.length();
		Long max = 0l;
		for(int i = 0; i < len; ++i){
			Node<N, A> node = this.nodes.get(i);
			for(Arc<N,A> arc : node.getLeft()){
				max = Math.max(max, arc.getId());
			}
			for(Arc<N,A> arc : node.getRight()){
				max = Math.max(max, arc.getId());					
			}
			
		}
		return max;
	}
}










